//
//  Copyright (C) 1998-2007 J. Andrew McLaughlin
// 
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
// 
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
//  for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  kernelInitialize.c
//

#include "kernelInitialize.h"
#include "kernelDebug.h"
#include "kernelDisk.h"
#include "kernelFile.h"
#include "kernelFilesystem.h"
#include "kernelImage.h"
#include "kernelMisc.h"
#include "kernelPage.h"
#include "kernelMemory.h"
#include "kernelText.h"
#include "kernelLog.h"
#include "mach/initialize.h"
#include "kernelMalloc.h"
#include "core1/kernelMultitasker.h"
#include "kernelParameters.h"
#include "kernelRandom.h"
#include "zinput/kernelKeyboard.h"
#include "kernelNetwork.h"
#include "kernelSysTimer.h"
#include "drivers/kernelUsbDriver.h"
#include "kernelUser.h"
#include "zwin/kernelWindow.h"
#include "kernelError.h"
#include "core0/kernelSpinLock.h"
#include "core0/kernelPerformanceCounter.h"
#include "core0/kernelMallocStatic.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>


static void writeLoaderLog(unsigned char *screen, int chars, int bytesPerChar)
{
  // Write the saved screen contents to a 'loader log'

  char *buffer = NULL;
  fileStream tmpFile;
  int count;

  buffer = kernelMalloc(chars);
  if (buffer == NULL)
    return;

  for (count = 0; count < chars; count ++)
    buffer[count] = screen[count * bytesPerChar];

  if (kernelFileStreamOpen("/system/loader.log",
			   (OPENMODE_WRITE | OPENMODE_CREATE |
			    OPENMODE_TRUNCATE), &tmpFile) < 0)
    {
      kernelFree(buffer);
      return;
    }

  kernelFileStreamWrite(&tmpFile, chars, buffer);
  kernelFileStreamClose(&tmpFile);
  kernelFree(buffer);
  return;
}


/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
//
// Below here, the functions are exported for external use
//
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

int kernelInitialize(unsigned kernelMemory)
{
	// Does a bunch of calls involved in initializing the kernel.
	// kernelMain passes all of the kernel's arguments to this function
	// for processing.  Returns 0 if successful, negative on error.

	int status;
	textScreen screen;
	int graphics = 0;
	char welcomeMessage[512];
	static char rootDiskName[DISK_MAX_NAMELENGTH];
	kernelDisk *rootDisk = NULL;
	char value[128];
	int networking = 0;
	int count;

	extern char *kernelVersion[];

	// The kernel config file.  We read it later on in this function
	extern variableList *kernelVariables;

	// The default colors
	extern color kernelDefaultForeground;
	extern color kernelDefaultBackground;
	extern color kernelDefaultDesktop;

	kernelMallocStaticInitialize1();
	kernelPerformanceCountersInitialize();
	kernelMallocStaticInitialize2();

	// Initialize the page manager
	status = kernelPageInitialize(kernelMemory);
	if (status < 0)
		return (status);

	// Initialize the memory manager
	status = kernelMemoryInitialize(kernelMemory);
	if (status < 0)
		return (status);

	kernelLock_Initialize();
	kernelInitializeSpinLock();

	// Initialize HAL
	status = machInitialize();
	if (status < 0)
	{
		kernelError("Hardware abstraction layer initialization failed");
		return (status);
	}

	// Do device initialization
	status = kernelDeviceInitialize();
	if (status < 0)
	{
		kernelError("Hardware initialization failed");
		return (status);
	}
/*
	// Initialize the interrupt vector tables and default handlers.  Note
	// that interrupts are not enabled here; that is done during hardware
	// enumeration after the Programmable Interrupt Controller has been
	// set up.
	status = kernelInterruptInitialize();
	if (status < 0)
	{
		kernelError("Interrupt vector initialization failed");
		return (status);
	}
*/
	// Initialize the text screen output.  This needs to be done after paging
	// has been initialized so that our screen memory can be mapped to a
	// virtual memory address.
	status = kernelTextInitialize(80, 50);
	if (status < 0)
		return (status);

	// Initialize debugging output
	kernelDebugInitialize();

	// Save the current screen
	kernelTextScreenSave(&screen);

	// Do display device detection
	status = kernelDeviceDetectDisplay();
	if (status < 0)
	{
		kernelError("Display initialization failed");
		return (status);
	}

	// Initialize kernel logging
	status = kernelLogInitialize();
	if (status < 0)
	{
		kernelTextPrintLine("Logging initialization failed");
		return (status);
	}

	// Disable console logging by default, since it fills up the screen
	// with unnecessary details.
	kernelLogSetToConsole(0);

	// Log a starting message
	sprintf(welcomeMessage, "%s %s\n"
		"Copyright (C) 1998-2008 J. Andrew McLaughlin\n"
		"Copyright (C) 2009-2010 Vadim Ushakov"
		, kernelVersion[0], kernelVersion[1]);
	kernelLog(welcomeMessage);

	// Print the welcome message.
	kernelTextPrintLine("%s\nStarting, one moment please...", welcomeMessage);

	// Initialize the multitasker
	status = kernelMultitaskerInitialize();
	if (status < 0)
	{
		kernelError("Multitasker initialization failed");
		return (status);
	}

	// Do general device detection
	status = kernelDeviceDetect();
	if (status < 0)
	{
		kernelError("Hardware detection failed");
		return (status);
	}

	// Initialize USB bus functions
	status = kernelUsbInitialize();
	if (status < 0)
	{
		kernelError("USB initialization failed");
		return (status);
	}

	// Initialize the kernel's random number generator.
	status = kernelRandomInitialize();
	if (status < 0)
	{
		kernelError("Random number initialization failed");
		return (status);
	}

	// Initialize the filesystem drivers
	status = kernelFilesystemDriversInitialize();
	if (status < 0)
	{
		kernelError("Filesystem drivers initialization failed");
		return (status);
	}

	// Initialize the disk functions.  This must be done AFTER the hardware
	// has been enumerated, and AFTER the drivers have been installed.
	status = kernelDiskInitialize();
	if (status < 0)
	{
		kernelError("Disk functions initialization failed");
		return (status);
	}

	// Get the name of the boot disk
	status = kernelDiskGetBoot(rootDiskName);
	kernelDebug(debug_misc, "Rootdisk name %s", rootDiskName);
	if (status < 0)
	{
		kernelError("Unable to determine boot device");
		return (status);
	}

	// Initialize the file management
	status = kernelFileInitialize();
	if (status < 0)
	{
		kernelError("Files functions initialization failed");
		return (status);
	}

	// Mount the root filesystem.
	status = kernelFilesystemMount(rootDiskName, "/");
	if (status < 0)
	{
		// If it's a CD, it might be some other than cd0.  Our boot loader can't
		// distinguish which one it is.  Try other possibilities.
		if (!strcmp(rootDiskName, "cd0"))
		{
			for (count = 1; count < MAXHARDDISKS; count ++)
			{
				sprintf(rootDiskName, "cd%d", count);
				if (kernelDiskGetByName(rootDiskName))
				{
					status = kernelFilesystemMount(rootDiskName, "/");
					if (status == 0)
						break;
				}
			}
		}

		if (status < 0)
		{
			kernelError("Mounting root filesystem failed");
			return (status = ERR_NOTINITIALIZED);
		}
	}

	kernelDebug(debug_misc, "Mounted rootdisk %s", rootDiskName);

	rootDisk = kernelDiskGetByName(rootDiskName);
	if (rootDisk == NULL)
	{
		kernelError("Couldn't get root disk \"%s\"", rootDiskName);
		return (status = ERR_INVALID);
	}

	// Read the kernel config file
	status = kernelConfigRead(DEFAULT_KERNEL_CONFIG, kernelVariables);
	if (status < 0)
		kernelVariables = NULL;

	if (kernelVariables != NULL)
	{
		kernelVariableListGet(kernelVariables, "gui", value, 128);
		kernelGraphicsUseGUI(!strncmp(value, "yes", 128));

		kernelVariableListGet(kernelVariables, "log.to_console", value, 128);
		kernelLogSetToConsole(!strncmp(value, "yes", 128));
	}

	// Are we in a graphics mode?
	graphics = kernelGraphicsAreEnabled();

	if (kernelVariables != NULL)
	{
		// Get the keyboard mapping
		kernelVariableListGet(kernelVariables, "keyboard.map", value, 128);
		if (value[0] != '\0')
			kernelKeyboardSetMap(value);

		if (graphics)
		{
			// Get the default color values, if they're set in this file
			kernelVariableListGet(kernelVariables, "foreground.color.red", value, 128);
			if (value[0] != '\0')
				kernelDefaultForeground.red = atoi(value);
			kernelVariableListGet(kernelVariables, "foreground.color.green", value, 128);
			if (value[0] != '\0')
				kernelDefaultForeground.green = atoi(value);
			kernelVariableListGet(kernelVariables, "foreground.color.blue", value, 128);
			if (value[0] != '\0')
				kernelDefaultForeground.blue = atoi(value);


			kernelVariableListGet(kernelVariables, "background.color.red", value, 128);
			if (value[0] != '\0')
				kernelDefaultBackground.red = atoi(value);
			kernelVariableListGet(kernelVariables, "background.color.green", value, 128);
			if (value[0] != '\0')
				kernelDefaultBackground.green = atoi(value);
			kernelVariableListGet(kernelVariables, "background.color.blue", value, 128);
			if (value[0] != '\0')
				kernelDefaultBackground.blue = atoi(value);

			kernelVariableListGet(kernelVariables, "desktop.color.red", value, 128);
			if (value[0] != '\0')
				kernelDefaultDesktop.red = atoi(value);
			kernelVariableListGet(kernelVariables, "desktop.color.green", value, 128);
			if (value[0] != '\0')
				kernelDefaultDesktop.green = atoi(value);
			kernelVariableListGet(kernelVariables, "desktop.color.blue", value, 128);
			if (value[0] != '\0')
				kernelDefaultDesktop.blue = atoi(value);
		}

		kernelVariableListGet(kernelVariables, "network", value, 128);
		if (!strncmp(value, "yes", 128))
			networking = 1;

		kernelVariableListGet(kernelVariables, "multitasker.timeslice", value, 128);
		if (value[0] != '\0')
			kernelMultitaskerSetTimeSlice(atoi(value));

		kernelVariableListGet(kernelVariables, "pit.cycle_count", value, 128);
		if (value[0] != '\0')
			kernelSysTimerSetupTimer(0, 2, atoi(value));

	}

	if (graphics)
	{
		// Clear the screen with our default background color
		kernelGraphicClearScreen(&kernelDefaultDesktop);

		// Initialize mouse operations
		status = kernelMouseInitialize();
		if (status < 0)
			kernelWarning("Mouse initialization failed");
	}

	// If the filesystem is not read-only, open a kernel log file
	if (!rootDisk->filesystem.readOnly)
	{
		status = kernelLogSetFile(DEFAULT_LOGFILE);
		if (status < 0)
			// Make a warning, but don't return error.  This is not fatal.
			kernelWarning("Unable to open the kernel log file");

		// Write the saved screen contents to a "boot log"
		writeLoaderLog(screen.data, (80 * 50), kernelTextGetConsoleOutput()->textArea->bytesPerChar);
	}
	if (screen.data)
		kernelMemoryRelease(screen.data);

	// Read the kernel's symbols from the kernel symbols file, if possible
	kernelReadSymbols(KERNEL_SYMBOLS_FILE);

	// Initialize network functions?
	if (networking)
	{
		status = kernelNetworkInitialize();
		if (status < 0)
		{
			kernelError("Network initialization failed");
			return (status = ERR_NOTINITIALIZED);
		}
	}

	// Initialize user functions
	status = kernelUserInitialize();
	if (status < 0)
	{
		kernelError("User functions initialization failed");
		return (status = ERR_NOTINITIALIZED);
	}

	// Start the window management system.  Don't bother checking for an
	// error code.
	if (graphics)
	{
		status = kernelWindowInitialize();
		if (status < 0)
			// Make a warning, but don't return error.  This is not fatal.
			kernelWarning("Unable to start the window manager");
	}
	else
		kernelTextPrint("\nGraphics are not enabled.  Operating in text mode.\n");

	// Done setup. Return success.
	return (status = 0);
}
